package com.distribution.system.openapi.interceptor;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.distribution.common.utils.StringUtils;
import com.distribution.common.utils.ip.IpUtils;
import com.distribution.system.openapi.domain.ApiResult;
import com.distribution.system.openapi.domain.Constant;
import com.distribution.system.openapi.service.CmccDaDownChannelInfoService;
import com.distribution.system.openapi.util.BodyReaderHttpServletRequestWrapper;
import com.distribution.system.openapi.util.HttpHelper;
import com.distribution.system.openapi.util.SignUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.time.LocalDateTime;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author: GodLu
 * @create: 2024-04-16 22:53
 * @description: 验证签名拦截器
 */
@Slf4j
public class SignInterceptor implements HandlerInterceptor {
    @Value("${openapi.sign.expireTime}")
    private long expireTime;//接口签名验证超时时间

    private String ip;
    private String requestURL;

    @Resource
    private CmccDaDownChannelInfoService cmccDaDownChannelInfoService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求ip
        ip = IpUtils.getIpAddr(request);
        requestURL = String.valueOf(request.getRequestURL());
        log.info("访问" + requestURL + "开放接口, 访问时间:" + LocalDateTime.now() + ",IP:" + ip);
        // 判断请求方式
        String method = request.getMethod();
        if ("POST".equals(method)) {
            log.info("POST请求进入...");
            // 获取请求Body参数，需要使用 BodyReaderHttpServletRequestWrapper进行处理
            // 否则会出现异常：I/O error while reading input message； nested exception is java.io.IOException: Stream closed
            // 原因就是在拦截器已经读取了请求体中的内容，这时候Request请求的流中已经没有了数据
            // 解决流只能读取一次的问题：先读取流，然后在将流重新写进去就行了
            ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
            String body = HttpHelper.getBodyString(requestWrapper);
            String bodyString = URLDecoder.decode(body, "utf-8");
            log.info("验签入参：{}",bodyString);
            if (StringUtils.isEmpty(bodyString)) {
                Map<String, Object> map = new HashMap<>();
                response.setCharacterEncoding("utf-8");
                PrintWriter writer = response.getWriter();
                response.setHeader("Content-type", "application/json;charset=UTF-8");
                map.put("code", 7000);
                map.put("msg", "请求参数不能为空");
                writer.print(JSONObject.toJSONString(map));
                writer.close();
                return false;
            }
            //System.out.println("bodyString = " + bodyString);
            // 解析参数转JSON格式
            /*String tirm = bodyString.replaceAll(" ", "");//去空格
            Map<String,Object> bodyStrMap = new HashMap<>();
            String[] split = tirm.substring(1,tirm.length() - 1).split(",");//分隔逗号
            for (String item : split) {
                String[] splits = item.split("=");
                if (splits.length == 2) {
                    bodyStrMap.put(item.split("=")[0], item.split("=")[1]);
                }
            }
            JSONObject requestJson = JSON.parseObject(bodyString);*/
            Map<String,Object> bodyStrMap = JSON.parseObject(bodyString,Map.class);
            // 验签
            boolean validation = checkSign(bodyStrMap, response);
            if (!validation) {
                return false;
            }
            log.info("POST请求验签通过...");
            // 验签成功
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
        if ("GET".equals(method)) {
            log.info("GET请求进入...");
            // 获取请求参数
            Map<String, String> allRequestParam = getAllRequestParam(request);
            Set<Map.Entry<String, String>> entries = allRequestParam.entrySet();
            // 参数转JSON格式
            Map<String,Object> paramsMap = new HashMap<>();
            entries.forEach(key -> {
                paramsMap.put(key.getKey(), key.getValue());
            });
            // 验签
            boolean validation = checkSign(paramsMap, response);
            if (!validation) {
                return false;
            }
            log.info("GET请求验签通过...");
            // 验签成功
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
        return false;
    }

    /**
     * 验证签名
     * @param bodyStrMap 请求Map
     * @param response 响应
     * @return 是否验签成功
     * @throws Exception 异常
     */
    private boolean checkSign(Map<String,Object> bodyStrMap,HttpServletResponse response)throws Exception {
        //设置响应配置
        response.setContentType("application/json");
        response.setCharacterEncoding("utf8");
        response.setHeader("Content-type", "application/json;charset=UTF-8");

        //获取验证参数

        String appId = null;
        Long timestamp = null;
        String nonce = null;
        String sign = null;
        try {
            appId = (String) bodyStrMap.get(Constant.APP_ID);
            timestamp = (Long) bodyStrMap.get(Constant.TIME_STAMP);
            //nonce = bodyStrMap.get(Constant.NONCE);
            sign = (String) bodyStrMap.get(Constant.SIGN);
        } catch (ClassCastException e) {
            log.info("开放接口请求失败,时间:" + LocalDateTime.now() + ",IP:" + ip + ",访问接口:" + requestURL + "错误信息:验签参数转换异常");
            response.getWriter().write(JSON.toJSONString(ApiResult.error(400,"验签参数转换异常")));
            return false;
        }

        // 校验参数
        if (!StringUtils.isNotBlank(appId)) {
            log.info("开放接口请求失败, 时间:" + LocalDateTime.now() + ",IP:" + ip + ",访问接口:" + requestURL + "错误信息:appid无效");
            response.getWriter().write(JSON.toJSONString(ApiResult.error(400,"appid无效")));
            //response.getOutputStream().write(JSON.toJSONString(ApiResult.error(400,"appid无效")).getBytes());
            return false;
        }
        if (null == timestamp) {
            log.info("开放接口请求失败,时间:" + LocalDateTime.now() + ",IP:" + ip + ",访问接口:" + requestURL + "错误信息:时间戳无效");
            response.getWriter().write(JSON.toJSONString(ApiResult.error(400,"时间戳无效")));
            return false;
        }
        if (StringUtils.isBlank(sign)) {
            log.info("开放接口请求失败,时间:" + LocalDateTime.now() + ",IP:" + ip + ",访问接口:" + requestURL + "错误信息:签名无效");
            response.getWriter().write(JSON.toJSONString(ApiResult.error(400,"签名无效")));
            return false;
        }
        // 请求不能超时
        long diff = System.currentTimeMillis() - timestamp;
        // 请求发起时间与当前时间超过expireTime，则接口请求过期
        if (diff > 1000 * expireTime) {
            log.info("开放接口请求失败,时间:" + LocalDateTime.now() + ",IP:" + ip + ",访问接口:" + requestURL + "错误信息:签名已失效");
            response.getWriter().write(JSON.toJSONString(ApiResult.error(400,"签名已失效")));
            return false;
        }
        // appSecret不能为空
        String appSecret = cmccDaDownChannelInfoService.getAppSecretByAppId(appId);
        if (appSecret == null) {
            log.info("开放接口请求失败,时间:" + LocalDateTime.now() + ",IP:" + ip + ",访问接口:" + requestURL + "错误信息:API账号不存在");
            response.getWriter().write(JSON.toJSONString(ApiResult.error(400,"API账号不存在")));
            return false;
        }

        // 验证签名
        log.info("调用方传过来的Sign:{}", sign);
        // 根据APPID查询的密钥进行重签
        String verifySign = SignUtils.getVerifySign(bodyStrMap,appSecret);
        // 后端生成的签名
        log.info("后端生成的Sign:{}", verifySign);
        // 校验签名
        if (!StringUtils.equals(verifySign, sign)) {// APPID查询的密钥进行签名 和 用户签名进行比对
            response.getWriter().write(JSON.toJSONString(ApiResult.error(400,"签名错误")));
            return false;
        }
        return true;
    }

    /**
    * @Author: GodLu
    * @Date: 2024/4/17 10:19
    * @Description: 获取客户端GET请求中所有的请求参数
    * @param request 请求
    * @return: Map<String,String>
    */
    private Map<String, String> getAllRequestParam(final HttpServletRequest request) {
        Map<String, String> res = new HashMap<String, String>();
        Enumeration<?> temp = request.getParameterNames();
        if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                //如果字段的值为空，判断若值为空，则删除这个字段>
                if (null == res.get(en) || "".equals(res.get(en))) {
                    res.remove(en);
                }
            }
        }
        return res;
    }
}

